#include <stdlib.h>
#include <errno.h>
#include <stdio.h>
#include <pthread.h>
#include <netinet/in.h>
#include <string.h>
#include <fcntl.h>
#include <dirent.h>
#include <arpa/inet.h>

#include "common.h"
#include "file_status.h"
#include "beacon.h"
#include "readConfig.h"

char buff[BUFFERSIZE];
int randint;
struct sockaddr_in serveradd;
int tcpConnect = 0;
int finish = 0;

std::string SERVER_DATA_DIR;
int PORT;
int UDPPORT;

void initial()
{
	printf("initial\n");

	DIR *pDir;
	struct dirent *ent;
	int i = 0;
	
	//从配置文件读取本地文件目录，TCP服务器端口，UDP服务器端口
	Config configSettings(ConfigFile);
	SERVER_DATA_DIR = configSettings.Read("SERVER_DATA_DIR", SERVER_DATA_DIR);
	PORT = configSettings.Read("PORT", PORT);
	UDPPORT = configSettings.Read("UDPPORT", UDPPORT);

	printf("data path:%s\n", SERVER_DATA_DIR.c_str());

	//对数据文件夹中的每个文件进行状态初始化
	pDir = opendir(SERVER_DATA_DIR.c_str());
	while((ent = readdir(pDir)) != NULL)
	{
		if(ent->d_type & DT_REG)
		{
			printf("add file: %s\n", ent->d_name);
			initial_file_st(files_st + i, ent->d_name);
			i++;
		}
	}

	//记录要传送的文件个数
	FILE_COUNT = i;
}

int send_file(struct file_st *fs, struct sockaddr_in *servaddr)
{
	int filefd;
	int count;
	int sendbyte;
	struct timeval timeout = {10, 0};
	int res;
	
	//获得TCP socket，并设置接收和发送超时选项
	int tcpSock = Socket(AF_INET, SOCK_STREAM, 0);
	setsockopt(tcpSock, SOL_SOCKET, SO_RCVTIMEO, (char *)&timeout, sizeof(struct timeval));
	setsockopt(tcpSock, SOL_SOCKET, SO_SNDTIMEO, (char *)&timeout, sizeof(struct timeval));
	
	//连接接收TCP服务器
	printf("connecting to %s port:%d\n", inet_ntoa(servaddr->sin_addr), ntohs(servaddr->sin_port));
	if(connect(tcpSock, (struct sockaddr *)servaddr, sizeof(*servaddr)) < 0)
	{
		perror("connect");
		return -1;
	}

	//设置tcpConnect为连接状态
	tcpConnect = 1;
	
	//准备发送文件状态
	printf("transferring file status:\n");
	bzero(buff, sizeof(buff));
	buff[4] = 0x05;
	
	//获取文件名
	char *filename = strrchr(fs->filename, '/');
	if(filename == NULL){
		filename = fs->filename;
	}
	
	//复制文件名到buff中，空出32位，作为数据长度之用
	strcpy(buff + 1 + sizeof(int32_t), filename);	
	
	int n = sprintf(buff + 2 /* 1字节为0x05，另一字节为文件名的结束符 */ + strlen(filename) + sizeof(int32_t), "%d,%d,%u,%d", fs->send_count, fs->last_rcv_id, fs->send_block, fs->size);
	printf("%d,%d,%u,%d", fs->send_count, fs->last_rcv_id, fs->send_block, fs->size);
	
	//send file status
	//计算数据总长度，放在TCP数据段的开始部分，是为了接收方能区分结束位置
	int32_t sendlen = 2 + strlen(filename) + n;
	memcpy(buff, &sendlen, sizeof(sendlen));
	
	//计算实际发送的数据段长度
	count = sendlen + sizeof(int32_t);
	
	//发送文件状态数据
	send(tcpSock, buff, count, 0);
	printf("send file status OK!\n");
	
	//receive reply of file status from server
	//接收方会发送1字节数据，表明收到的数据长度
	sendbyte = 0;	
	recv(tcpSock, &sendbyte, sizeof(sendbyte), 0);
	printf("file status receive by server OK!\n");
	
	printf("will transfer file: %s\n", fs->filename);
	//open the file will be sent
	//获取文件的完整路径
	char pathname[512];	
	int len = sprintf(pathname, "%s/", SERVER_DATA_DIR.c_str());
	sprintf(pathname + len, "%s", fs->filename);
	
	//打开文件
	filefd = open(pathname, O_RDONLY);
	printf("open file %s \n", pathname);
	
	//跳过已经发送的数据部分
	if(fs->send_block != 0)
	{
		lseek(filefd, fs->send_block, SEEK_SET);
	}
	
	bzero(buff, sizeof(buff));
	buff[4] = 0x03;
	unsigned int senddata = 0;
	int fcount;
	while((fcount =  read(filefd, buff + 1 + sizeof(int32_t), sizeof(buff) - 1 - sizeof(int32_t))) > 0)
	{
		//send file data
		int32_t sendlen = fcount + 1;
		memcpy(buff, &sendlen, sizeof(sendlen));
		count = sendlen + sizeof(int32_t);
		send(tcpSock, buff, count, 0);
		//printf("send %d bytes\n", count);

		//receive reply of file data
		sendbyte = 0;
		recv(tcpSock, &sendbyte, sizeof(sendbyte), 0);
		//printf("recv data reply %d bytes\n",res);
		//printf("sendbyte:%d,count:%d\n",sendbyte, count);
		//判断发送数据的长度和接收方接收的数据长度来判断是否有传输错误
		if(sendbyte != count)
		{
			printf("data does not receive correctly by server.\n");
			break;
		}
		
		//确认没有错误后，更新本次发送的数据长度
		senddata += (sendbyte - 1 - sizeof(int32_t));	

		bzero(buff, sizeof(buff));
		buff[4] = 0x03;
	}
	
	//已到达文件尾
	if(fcount == 0)
	{
		//该文件已传送完毕，下一次不会被传送
		fs->finish = 1;
		
		//给发送方发送结束标志
		bzero(buff, sizeof(buff));
		buff[4] = 0x04;
		int32_t sendlen = 1;
		memcpy(buff, &sendlen, sizeof(sendlen));
		send(tcpSock, buff, 1 + sizeof(int32_t), 0);
		recv(tcpSock, &sendbyte, sizeof(sendbyte), 0);
		
		res = 1;
	}
	
	//发送过程中有错误，更新相关数据
	if(senddata > 0)
	{
		fs->send_count++;
		fs->last_rcv_id = ntohl(servaddr->sin_addr.s_addr)%256;
		fs->send_block += senddata;
		fs->size -= senddata;
		
		res = -1;
	}
	
	//关闭文件和socket
	close(filefd);
	close(tcpSock);
	
	//更新tcpConnect为断开状态
	tcpConnect = 0;
	
	return res;
}

void send_files(struct sockaddr_in *servaddr)
{
	int i;
	
	for(i = 0; i < FILE_COUNT; i++)
	{
		if(files_st[i].finish == 0)
		{
			send_file(files_st + i, servaddr);
		}
	}
}

void *udpServer(void *null)
{
	struct sockaddr_in my_addr;
	struct sockaddr_in remote_addr;
	socklen_t addrlen;
	int udpServerSockFd;
	int on = 1;

	udpServerSockFd = Socket(AF_INET, SOCK_DGRAM, 0);
	setsockopt(udpServerSockFd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on));
	
	bzero(&my_addr, sizeof(my_addr));
	my_addr.sin_family = AF_INET;
	my_addr.sin_addr.s_addr = htonl(INADDR_ANY);
	my_addr.sin_port = htons(UDPPORT);
	Bind(udpServerSockFd, (struct sockaddr *)&my_addr, sizeof(struct sockaddr));

	while(1)
	{
		bzero(buff, sizeof(buff));
		Recvfrom(udpServerSockFd, buff, sizeof(buff), 0, (struct sockaddr *)&remote_addr, &addrlen);
		
		int recvint;
		sscanf(buff + 1, "%d", &recvint);
		printf("buff[0]:%x,recvint:%d\n", buff[0], recvint);
		
		if(buff[0] == 0x02 && recvint == randint)
		{
			printf("recv reply from %s\n",inet_ntoa(remote_addr.sin_addr));
			remote_addr.sin_port = htons(PORT);
			send_files(&remote_addr);
		}
	}
	Close(udpServerSockFd);
}

int main()
{
	pthread_t id;
	pthread_attr_t attr;
	pthread_attr_init(&attr);
	pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
	
	initial();

	srand((unsigned)time(NULL));
	randint = rand();

	if(pthread_create(&id, &attr, udpServer, NULL) !=0 )
	{
		printf("recv start error!%s\n", strerror(errno));
		abort();
		exit(1);
	}
	while(1)
	{
		if(!tcpConnect)
		{
			beacon(randint);
		}
		sleep(1);
	}
}
